#!/usr/bin/python

from xmlrpclib import _Method,  Transport,  SafeTransport, ServerProxy, dumps
import pickle
import threading
import time
import sys

# on python >=2.4 import the deque class from collections
try:
    from collections import deque
except:
    # if collections is not found, use own deque replacement
    from mdeque import deque


class BSPWorkerThread(threading.Thread):
    """"
    This is the worker thread for the BufferedServerProxy
    it is responsible for getting the fifo emptied.
    There should be only one instance of this class, having two
    or more will end up in some kind of chaos. so be aware
    """
    def __init__(self,  parent):
        # calling supperclass of Thread for having everything in place for the
        # worker thread
        
        threading.Thread.__init__(self)
        
        # initializing some variables and getting informations from the parent
        # BufferedServerProxy
        
        self.parent = parent
        self.__stopworkerthread =0
        
    def run(self):
        
        # main loop of the worker thread which empties the 
        # fifo buffer containing the calls which didnt succeed.
        
        while self.parent._BufferedServerProxy__stopworkerthread == 0:
            
            connected = 1
            
            # getting exclusive lock on the fifo
            self.parent._BufferedServerProxy__fifolock.acquire()
            
            # getting number of pending calls inside the fifo
            fifosize = len(self.parent._BufferedServerProxy__fifo)
            
            if self.parent._BufferedServerProxy__debug:
                print "FIFO size: %i" % fifosize 
     
            # stop worker thread if fifo is empty
            if fifosize == 0:
                self.parent._BufferedServerProxy__stopworkerthread = 1
                break
                
            # releasing exclusive lock on the fifo    
            self.parent._BufferedServerProxy__fifolock.release()
            
            # try to execute the buffered calls:
            while connected:
                    # getting exclusive lock on the fifo
                    self.parent._BufferedServerProxy__fifolock.acquire()
                    
                    # getting first element in the fifo
                    if len(self.parent._BufferedServerProxy__fifo)>=1:
                        methodname ,  params = self.parent._BufferedServerProxy__fifo[0]
                        try:
                            # try executing the method call
                            response = self.parent._BufferedServerProxy__execrequest(methodname, params )
            
                            # removing executed method call from fifo and update file
                            self.parent._BufferedServerProxy__fifo.popleft()
                            self.parent._BufferedServerProxy__writetofile()
                            
                        except:
                            # server not available , leave loop and try 
                            # again later
                            connected = 0
                    else:
                        # fifo empty, leave loop
                        connected = 0
                    
                    # releasing exclusive lock on the fifo    
                    self.parent._BufferedServerProxy__fifolock.release()
            
                    # wait some time bevore executing next call
                    # from the fifo 
                    time.sleep(self.parent._BufferedServerProxy__callpausetime)
            
            # wait some time before retry
            time.sleep(self.parent._BufferedServerProxy__retrytime)
        
        
class BufferedServerProxy(ServerProxy):
    """uri [,options] -> a logical connection to an XML-RPC server

    uri is the connection point on the server, given as
    scheme://host/target.

    The standard implementation always supports the "http" scheme.  If
    SSL socket support is available (Python 2.0), it also supports
    "https".

    If the target part and the slash preceding it are both omitted,
    "/RPC2" is assumed.

    The following options can be given as keyword arguments:

        transport: a transport factory
        encoding: the request encoding (default is UTF-8)

    All 8-bit strings passed to the server proxy are assumed to use
    the given encoding.
    """

    def __init__(self, uri, bufferfile = "xmlrpcbuffer.txt", **args):
        
        
        #initialize some variables
        self.__bufferfile = bufferfile
        self.__stopworkerthread = 0
        self.__debug = 0
        self.__workerthread = None
        
        # setting the time to wait between method calls 
        # for the worker thread
        self.__callpausetime = .1
        
        # setting the reconnection time interval for the 
        # worker thread
        self.__retrytime = 1
        
        # creating the fifo buffer as a double ended queue so that it can be
        # accessed from both sides ( left and right )
        
        self.__fifo = deque()
        
        # creating a mutex based lock for stearing the access to the fifo
        # and avoid 2 threads accessing it at the same time
        
        self.__fifolock = threading.Lock()
        
        # create fifo buffer if needed
        
        try:
            fd = open(self.__bufferfile, "r")
            self.__fifo = pickle.load(fd )
            fd.close()
        except:
            self.__writetofile()
        
        ServerProxy.__init__(self,uri)
        
        # start the worker thread if something is left in the fifo.
        
        if len(self.__fifo):
            self.__startworkerthread()
        
    def __startworkerthread(self):
        # This function creats a worker thread and starts it
        self.__workerthread = BSPWorkerThread(self)
        self.__workerthread.start()

    def __request(self, methodname, params):
        print "__request"
        # this function is called whenever a method is called which is
        # not existing in this class and gets forwarded to the xmlrpc
        # server. If the server cannot be reached, the method call is
        # put into the fifo buffer for later execution.
        
        # getting exclusive lock on the fifo
        self.__fifolock.acquire()
    
        # appending method call to the fifo
        self.__fifo.append([methodname, params])
        
        # write fifo contents to the disk
        self.__writetofile()
        
        
        try:
        
            # try executing the method call
            if self.__debug:
                print "try to contact server"
            response = self.__execrequest(methodname, params )
            if self.__debug:
                print "success"
            
            # removing executed method call from fifo and update file
            self.__fifo.pop()
            self.__writetofile()
            
        except:
            if self.__debug:
                print sys.exc_info()
            response = -1
        
        # start the worker thread if something is left in the fifo.
        
        if len(self.__fifo):
            self.__startworkerthread()
        
        # releasing exclusive lock on the buffer
        self.__fifolock.release()
        
        return response
            
    def __execrequest(self,methodname, params):
        
        # call a method on the remote server through xmlrpc
        if self.__debug:
            print "__execrequest->dumps"
        
        request = dumps(params, methodname, encoding=self._ServerProxy__encoding, allow_none=self._ServerProxy__allow_none)
    
        if self.__debug:
            print "__execrequest->transport"
        response = self._ServerProxy__transport.request(self._ServerProxy__host,self._ServerProxy__handler,request,verbose=self._ServerProxy__verbose)

        if len(response) == 1:
            response = response[0]

        return response
    
    def __repr__(self):
        
        # helping function to display the objecttype when the object instance
        # is used with the print command. It also displays the number of not
        # executed method calls remaining in the fifo buffer
        host = self._ServerProxy__host
        handler = self._ServerProxy__handler
        fifosize = len(self.__fifo)
        ret = "<BufferedServerProxy for %s%s FIFO contains %i method calls>" %   (host, handler, fifosize)
        return (ret)

    __str__ = __repr__

    def __getattr__(self, name):
        # magic method dispatcher
        return _Method(self.__request, name)

    # note: to call a remote object with an non-standard name, use
    # result getattr(server, "strange-python-name")(args)
        
        
    def __writetofile(self):
        
        # this function dumps the contents of the fifo buffer to 
        # the buffer file
        
        fd = open(self.__bufferfile, "w")
        pickle.dump(self.__fifo, fd)
        fd.close()
        
    def stop(self):
        self.__stopworkerthread = 1
        
        
    
# just some test example     
if __name__ == "__main__":
        
    srv = BufferedServerProxy("http://localhost:8000")
    print
    print srv
    print srv.testinc(2)
    print srv
    srv.stop()


